
/* GCSx
** BYTECODE.CPP
**
** Bytecode-related utilities/datatypes/structures
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

string dataTypeToWart(const DataType& dt) { start_func
    string result = string(1, dt.baseType + 'C');
    result += 'A' + dt.flags;
    if (((dt.baseType == DATA_ENTITY) || (dt.baseType == DATA_OBJECT)) && (dt.subType))
        result = result + formatString("%X", dt.subType);
    return result;
}

const char* wartToDataType(const char* wart, DataType& result) { start_func
    assert(wart);
    
    int base = *wart++ - 'C';    
    if ((base != DATA_VOID) && (base != DATA_VAR) && (base != DATA_INT) &&
        (base != DATA_FLOAT) && (base != DATA_STR) && (base != DATA_ENTITY) &&
        (base != DATA_OBJECT))
        return NULL;
    result.baseType = base;
    
    int flags = *wart++ - 'A';
    if ((flags != 0) && (flags != DATA_ARRAY) && (flags != DATA_HASH) &&
        (flags != DATA_CONST))
        return NULL;
    result.flags = flags;
    
    char* endptr;
    result.subType = strtol(wart, &endptr, 16);
    return endptr;
}

void destroyLabelMap(LabelMap* map) { start_func
    if (map) {
        // We clear one at a time to ensure we can delete hash keys at the right times
        LabelMap::iterator pos = map->begin();
        while (pos != map->end()) {
            const char* toDel = (*pos).first;
            map->erase(pos);
            delete[] toDel;
            pos = map->begin();
        }
    }
}

list<Label>& addLabelMap(LabelMap* map, const string& name) { start_func
    const char* cName = name.c_str();
    // (create new element with new name allocation?)
    if (map->find(cName) == map->end()) cName = newCpCopy(name);
    // Find or create new element
    return (*map)[cName];
}

void destroyFunctionMap(FunctionMap* map) { start_func
    if (map) {
        // We clear one at a time to ensure we can delete hash keys at the right times
        FunctionMap::iterator pos = map->begin();
        while (pos != map->end()) {
            const char* toDel = (*pos).first;
            delete[] (*pos).second.parameters;
            map->erase(pos);
            delete[] toDel;
            pos = map->begin();
        }
    }
}

Function& addFunctionMap(FunctionMap* map, const string& name) { start_func
    const char* cName = name.c_str();
    // (create new element with new name allocation?)
    if (map->find(cName) == map->end()) cName = newCpCopy(name);
    // Find or create new element
    return (*map)[cName];
}

void destroyVariableMap(VariableMap* map) { start_func
    if (map) {
        // We clear one at a time to ensure we can delete hash keys at the right times
        VariableMap::iterator pos = map->begin();
        while (pos != map->end()) {
            const char* toDel = (*pos).first;
            for (list<Variable>::iterator pos2 = (*pos).second.begin(); pos2 != (*pos).second.end(); ++pos2) {
                if (((*pos2).datatype.baseType == DATA_STR) &&
                    ((*pos2).datatype.flags == DATA_CONST))
                    delete[] (*pos2).val.constS;
            }
            map->erase(pos);
            delete[] toDel;
            pos = map->begin();
        }
    }
}

list<Variable>& addVariableMap(VariableMap* map, const string& name, const char*& storeName) { start_func
    const char* cName = name.c_str();
    VariableMap::iterator found = map->find(cName);
    // (create new element with new name allocation?)
    if (found == map->end()) {
        storeName = cName = newCpCopy(name);
        return (*map)[cName];
    }
    // Existing element and name allocation
    else {
        storeName = (*found).first;
        return (*found).second;
    }
}

#ifdef COMPILEASSERT
void checkOpcode(Opcode opc, Uint8 mode1, Uint8 mode2, Uint8 mode3) { start_func
    // Basic checks
    bytecodeAssert((opc >= 0) && (opc < OP_LAST));

    // More detailed checks- validate mode combinations are legal
    Uint8 modes[3] = { mode1, mode2, mode3 };
    for (int pos = 0; pos < 3; ++pos) {
        Uint8 check = modes[pos];
        Uint8 basetype = check & OM_BASETYPE;
        
        // Reserved (unallocated) types or non-allowed literal types
        if (OM_IS_LITERAL(check)) {
            bytecodeAssert(basetype != OM_STR);
            bytecodeAssert(basetype != OM_ARRAY);
            bytecodeAssert(basetype != OM_HASH);
            bytecodeAssert((basetype <= OM_THIS) || (basetype == OM_POINTER));
            
        }
        else {
            bytecodeAssert(basetype < OM_RESERVED_1);
        }
        
        // Cannot use OM_INDIRECT with entry, array, hash, local, global (or const, see below)
        if (OM_IS_INDIRECT(check)) {
            bytecodeAssert(basetype != OM_ENTRY);
            bytecodeAssert(basetype != OM_ARRAY);
            bytecodeAssert(basetype != OM_HASH);
            bytecodeAssert(!OM_IS_LOCAL(check));
            bytecodeAssert(!OM_IS_GLOBAL(check));
        }

        // Cannot use OM_POP with entry, non-const, or convert
        if (OM_IS_POP(check)) {
            bytecodeAssert(basetype != OM_ENTRY);
            bytecodeAssert(basetype != OM_STR);
            bytecodeAssert(OM_IS_COPY(check) || OM_IS_KNOWN(check) || OM_IS_INDIRECT(check));
        }
        
        // CONST str must be copy, not convert
        // Regular str must be convert, not copy
        if (basetype == OM_STR) {
            bytecodeAssert(!OM_IS_COPY(check));
        }
        else if (basetype == OM_STR_CONST && !OM_IS_LITERAL(check)) {
            bytecodeAssert(OM_IS_COPY(check) || OM_IS_KNOWN(check) || OM_IS_INDIRECT(check));
        }
    }

    // @TODO: redo bytecode checks below using newer #defines
    
/*
    // Most detailed of checks- validate mode combination for specific opcode
    int numP = 1;
    int goalTemplate = 0;
    int goalConst1 = 0;
    int goalConst2 = 0;
    int goalType;
    switch (opc) {
        case OP_NOOP:
        case OP_INIT:
        case OP_LOGAND:
        case OP_LOGOR:
        case OP_NOT:
        case OP_PRECALL:
        case OP_STOP:
        case OP_WAIT: {
            // No parameters
            bytecodeAssert(mode1 == OM_NONE);
            break;
        }
        
        case OP_DEBUG: // @TODO: debug changed use; replace with something else
        case OP_RET: {
            bytecodeAssert(mode1 == OM_INT);
            bytecodeAssert(mode2 == OM_INT);
            numP = 2;
            break;
        }
    
        case OP_CALL: {
            bytecodeAssert(mode1 == OM_CFUNCTION);
            break;
        }
        
        case OP_FORCEARRAY:
        case OP_FORCEHASH: {
            bytecodeAssert(mode1 == OM_STACK);
            bytecodeAssert(mode2 == OM_INT);
            numP = 2;
            break;
        }

        case OP_ITOF:
        case OP_ITOS:
        case OP_FTOI:
        case OP_FTOS:
        case OP_STOI:
        case OP_STOF:
        case OP_FORCEFLOAT:
        case OP_FORCEINT:
        case OP_FORCESTRING: {
            bytecodeAssert(mode1 == OM_STACK);
            break;
        }

        case OP_IFFALSE:
        case OP_IFTRUE: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](#)
            // 2) [Offset]
            bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode1 & OM_POP) == OM_POP));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_INT);
            bytecodeAssert(mode2 == OM_OFFSET);
            numP = 2;
            break;
        }

        case OP_IFFALSEf:
        case OP_IFTRUEf: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](%)
            // 2) [Offset]
            bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode1 & OM_POP) == OM_POP));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_FLOAT);
            bytecodeAssert(mode2 == OM_OFFSET);
            numP = 2;
            break;
        }
        
        case OP_JUMP: {
            bytecodeAssert(mode1 == OM_OFFSET);
            break;
        }
        
        case OP_CREATEARRAY:
        case OP_CREATEHASH:
        case OP_REPLY: {
            bytecodeAssert(mode1 == OM_INT);
            break;
        }
        
        case OP_SUBR: {
            bytecodeAssert((mode1 == OM_OFFSET) ||
                           (mode1 == OM_FUNCTION));
            break;
        }
        
        case OP_SWAP: {
            bytecodeAssert(mode1 == OM_STACK);
            bytecodeAssert(mode2 == OM_STACK);
            numP = 2;
            break;
        }
        
        case OP_TYPEOF:
        case OP_COPY:
        case OP_PUSHv:
        case OP_POPv: {
            bytecodeAssert((mode1 == OM_STACK) ||
                           (mode1 == OM_LOCAL) ||
                           (mode1 == OM_GLOBAL));
            break;
        }
        
        case OP_DISCARD:
        case OP_DISCARDL: {
            // [Pop]/[Stack]/[Local]/[Ptr](#) or [Int]
            bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode1 & OM_POP) == OM_POP) ||
                           (mode1 == OM_INT));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_INT);
            break;
        }
        
        case OP_ARRAY:
        case OP_POPARRAY: {
            // 1) [Stack]/[Local]/[Ptr](a)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](#) or [Int]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_ARRAY);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == OM_INT));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_INT);
            numP = 2;
            break;
        }
        
        case OP_HASH:
        case OP_POPHASH: {
            // 1) [Stack]/[Local]/[Ptr](h)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_HASH);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == OM_STR));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_STR);
            goalConst2 = 1;
            numP = 2;
            break;
        }
        
        case OP_CONCAT: {
            // 1) [Stack]/[Local]/[Ptr]($)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_STR);
            bytecodeAssert(!(mode1 & OM_CONST));
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == OM_STR));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_STR);
            goalConst2 = 1;
            numP = 2;
            break;
        }
        
        case OP_QUERY:
        case OP_SEND: {
            // 1) [Stack]/[Local]/[Ptr](#)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            // 3) [Int]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_INT);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == OM_STR));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_STR);
            bytecodeAssert(mode3 == OM_INT);
            goalConst2 = 1;
            numP = 3;
            break;
       }
        
        case OP_QUERYs:
        case OP_SENDs: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            // 2) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            // 3) [Int]
            bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode1 & OM_POP) == OM_POP) ||
                           (mode1 == OM_STR));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_STR);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == OM_STR));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_STR);
            bytecodeAssert(mode3 == OM_INT);
            goalConst1 = 1;
            goalConst2 = 1;
            numP = 3;
            break;
        }
        
        case OP_SUBRs: {
            // 1) [Stack]/[Local]/[Ptr]($)
            // 2) [Int]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_STR);
            bytecodeAssert(!(mode1 & OM_CONST));
            bytecodeAssert(mode2 == OM_INT);
            numP = 2;
            break;
        }
        
        case OP_STORE: {
            // 1) [Stack]/[Local]/[Ptr](#)
            // 2) [Stack]/[Local]/[Ptr](#) or [Int]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_INT);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           (mode2 == OM_INT));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_INT);
            numP = 2;
            break;
        }
        
        case OP_STOREf: {
            // 1) [Stack]/[Local]/[Ptr](%)
            // 2) [Stack]/[Local]/[Ptr](%) or [Float]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_FLOAT);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           (mode2 == OM_FLOAT));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_FLOAT);
            numP = 2;
            break;
        }
        
        case OP_STOREs: {
            // 1) [Stack]/[Local]/[Ptr]($)
            // 2) [Stack]/[Local]/[Ptr](c$) or [String]
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == OM_STR);
            bytecodeAssert(!(mode1 & OM_CONST));
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           (mode2 == OM_STR));
            bytecodeAssert((mode2 & OM_BASETYPE) == OM_STR);
            goalConst2 = 1;
            numP = 2;
            break;
        }
        
        case OP_STOREa: {
            // 1) [Stack]/[Local]/[Ptr](a)
            // 2) [Stack]/[Local]/[Ptr](a)
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_ARRAY);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            bytecodeAssert(mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode2 & OM_ARRAY);
            bytecodeAssert((mode2 & OM_POP) != OM_POP);
            numP = 2;
            break;
        }
        
        case OP_STOREh: {
            // 1) [Stack]/[Local]/[Ptr](h)
            // 2) [Stack]/[Local]/[Ptr](h)
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_HASH);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            bytecodeAssert(mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode2 & OM_HASH);
            bytecodeAssert((mode2 & OM_POP) != OM_POP);
            numP = 2;
            break;
        }
        
        case OP_NEG:
        case OP_POP: {
            // [Stack]/[Local]/[Ptr](#)
            goalType = OM_INT;
            goalTemplate = 4;
            break;
        }

        case OP_NEGf:
        case OP_POPf: {
            // [Stack]/[Local]/[Ptr](%)
            goalType = OM_FLOAT;
            goalTemplate = 4;
            break;
        }
        
        case OP_POPs: {
            // [Stack]/[Local]/[Ptr]($)
            bytecodeAssert(!(mode1 & OM_CONST));
            goalType = OM_STR;
            goalTemplate = 4;
            break;
        }

        case OP_PUSH: {
            // [Stack]/[Local]/[Ptr](#) or [Int]
            goalType = OM_INT;
            goalTemplate = 3;
            break;
        }

        case OP_PUSHf: {
            // [Stack]/[Local]/[Ptr](%) or [Float]
            goalType = OM_FLOAT;
            goalTemplate = 3;
            break;
        }

        case OP_PUSHs: {
            // [Stack]/[Local]/[Ptr](c$) or [String]
            goalType = OM_STR;
            goalTemplate = 3;
            goalConst1 = 1;
            break;
        }

        case OP_POPa:
        case OP_PUSHa: {
            // [Stack]/[Local]/[Ptr](a)
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_ARRAY);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            break;
        }

        case OP_POPh:
        case OP_PUSHh: {
            // [Stack]/[Local]/[Ptr](h)
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert(mode1 & OM_HASH);
            bytecodeAssert((mode1 & OM_POP) != OM_POP);
            break;
        }
        
        case OP_ADD:
        case OP_AND:
        case OP_DIV:
        case OP_MOD:
        case OP_MULT:
        case OP_OR:
        case OP_SHIFTL:
        case OP_SHIFTR:
        case OP_SUB:
        case OP_XOR: {
            // 1) [Stack]/[Local]/[Ptr](#)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](#) or [Int]
            goalType = OM_INT;
            goalTemplate = 1;
            break;
        }
        
        case OP_EQ:
        case OP_GE:
        case OP_GT:
        case OP_LE:
        case OP_LT:
        case OP_NE: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](#)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](#) or [Int]
            goalType = OM_INT;
            goalTemplate = 2;
            break;
        }
        
        case OP_ADDf:
        case OP_DIVf:
        case OP_MULTf:
        case OP_SUBf: {
            // 1) [Stack]/[Local]/[Ptr](%)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](%) or [Float]
            goalType = OM_FLOAT;
            goalTemplate = 1;
            break;
        }
        
        case OP_EQf:
        case OP_GEf:
        case OP_GTf:
        case OP_LEf:
        case OP_LTf:
        case OP_NEf: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](%)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](%) or [Float]
            goalType = OM_FLOAT;
            goalTemplate = 2;
            break;
        }

        case OP_EQs:
        case OP_GEs:
        case OP_GTs:
        case OP_LEs:
        case OP_LTs:
        case OP_NEs: {
            // 1) [Pop]/[Stack]/[Local]/[Ptr](c$)
            // 2) [Pop]/[Stack]/[Local]/[Ptr](c$) or [String]
            goalType = OM_STR;
            goalTemplate = 2;
            goalConst1 = 1;
            goalConst2 = 1;
            break;
        }
        
        // To prevent warning
        case OP_LAST:
            bytecodeAssert(0);
            break;
    }
    
    // Some standardized templates
    switch (goalTemplate) {
        case 1:
        case 2: {
            // gT1)
            //  p1) [Stack]/[Local]/[Ptr](type)
            //  p2) [Pop]/[Stack]/[Local]/[Ptr](type) or [type]
            // gT2) same but p1) can be [Pop]
            if (goalTemplate == 1)
                bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            else
                bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                               ((mode1 & OM_POP) == OM_POP));
            bytecodeAssert((mode1 & OM_BASETYPE) == goalType);
            bytecodeAssert((mode2 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           ((mode2 & OM_POP) == OM_POP) ||
                           (mode2 == (mode2 & OM_BASETYPE)));
            bytecodeAssert((mode2 & OM_BASETYPE) == goalType);
            numP = 2;
            break;
        }
    
        case 3: {
            // [Stack]/[Local]/[Ptr](type) or [type]
            bytecodeAssert((mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK)) ||
                           (mode1 == (mode1 & OM_BASETYPE)));
            bytecodeAssert((mode1 & OM_BASETYPE) == goalType);
            break;
        }
    
        case 4: {
            // [Stack]/[Local]/[Ptr](type)
            bytecodeAssert(mode1 & (OM_LOCAL | OM_GLOBAL | OM_STACK));
            bytecodeAssert((mode1 & OM_BASETYPE) == goalType);
            break;
        }
    }
    
    if (goalConst1)
        bytecodeAssert((mode1 == OM_STR) || (mode1 & OM_CONST));
    if (goalConst2)
        bytecodeAssert((mode2 == OM_STR) || (mode2 & OM_CONST));
    
    if (numP < 3) bytecodeAssert(mode3 == OM_NONE);
    if (numP < 2) bytecodeAssert(mode2 == OM_NONE);
*/
}
#endif

const char* opcodeNames[] = {
    "NOOP    ",
    "ADD     ",
    "ADDf    ",
    "AND     ",
    "CONCAT  ",
    "CONVERT ",
    "CREATEa ",
    "CREATEe ",
    "CREATEh ",
    "CREATEo ",
    "DEBUG   ",
    "DISCARD ",
    "DISCARDL",
    "DIV     ",
    "DIVf    ",
    "EQ      ",
    "EQf     ",
    "EQo     ",
    "EQs     ",
    "GE      ",
    "GEf     ",
    "GEs     ",
    "GT      ",
    "GTf     ",
    "GTs     ",
    "IFFALSE ",
    "IFFALSEa",
    "IFFALSEf",
    "IFFALSEh",
    "IFFALSEo",
    "IFFALSEs",
    "IFFALSEv",
    "IFTRUE  ",
    "IFTRUEa ",
    "IFTRUEf ",
    "IFTRUEh ",
    "IFTRUEo ",
    "IFTRUEs ",
    "IFTRUEv ",
    "INIT    ",
    "JUMP    ",
    "LE      ",
    "LEf     ",
    "LEs     ",
    "LT      ",
    "LTf     ",
    "LTs     ",
    "MOD     ",
    "MULT    ",
    "MULTf   ",
    "NE      ",
    "NEf     ",
    "NEo     ",
    "NEs     ",
    "OR      ",
    "PUSH    ",
    "PUSHa   ",
    "PUSHf   ",
    "PUSHh   ",
    "PUSHo   ",
    "PUSHs   ",
    "PUSHv   ",
    "RET     ",
    "SHIFTL  ",
    "SHIFTR  ",
    "STOP    ",
    "STORE   ",
    "STOREa  ",
    "STOREf  ",
    "STOREh  ",
    "STOREo  ",
    "STOREs  ",
    "STOREv  ",
    "SUB     ",
    "SUBf    ",
    "SUBR    ",
    "XOR     ",
    "GETe    ",
    "SETe    ",
    "GETo    ",
    "SETo    ",
    "IDLE    ",
    "RETVOID ",
    "SETef   ",
    "SETei   ",
    "SETof   ",
    "SEToi   ",
};

typedef map<int, const LinkEntry*> LinkMap;
LinkMap* linkMap = NULL;
int linkPos = 0;

void decompileBytecode(const Uint32* bytecode, const list<LinkEntry>* links) { start_func
    assert(bytecode);
    assert(links);

    Uint32 size = *bytecode++;
    Uint32 strOffset = *bytecode++;
    Uint32 pos = 0;
    
    debugWrite("Code: %d dw; Strings: %d dw; Links: %d", strOffset, size - strOffset, links->size());
    
    // Turn links into a map by position
    linkMap = new LinkMap;
    list<LinkEntry>::const_iterator end = links->end();
    for (list<LinkEntry>::const_iterator pos = links->begin(); pos != end; ++pos) {
        (*linkMap)[(*pos).offset] = &(*pos);
    }

    while (pos < strOffset) {
        string line = formatString("%04X ", pos);
        linkPos = pos;
        int sz = debugBytecode(line, bytecode);
        pos += sz;
        bytecode += sz;
        debugWrite("%s", line.c_str());
    }
    
    delete linkMap;
    linkMap = NULL;
    
    // Display strings
    while (pos < size) {
        debugWrite("%04X %s", pos, (const char*)bytecode);
        int sz = strlen((const char*)bytecode) / 4 + 1;
        pos += sz;
        bytecode += sz;
    }
}

int debugBytecode(string& line, const Uint32* bytecode) { start_func
    int used = 1;
    
    // Opcode
    Uint32 opcode = *bytecode++;
    line += opcodeNames[opcode & 0xFF];

    // Operands
    for (int o = 0; o < 3; ++o) {
        opcode >>= 8;
        Uint8 addrmode = opcode & 0xFF;
        
        if (addrmode) {
            // All operand modes pull a single 32bit operand out of bytecode except-
            //   pop or 'none/all/this/all_other'- pull nothing
            //   obj_entity+subtype- pulls an extra 32bit for subtype FIRST
            //   literal/global pointer- pulls pointer size (may be 32bit or 64bit etc)
            //   literal float- pulls float size (may be 32bit or more)
                        
            if (OM_IS_POINTER(addrmode)) {
                void* operand = *(void**)(bytecode);
                bytecode += vPtrSize;
                used += vPtrSize;
                
                line += " [Ptr]";
                // (only show address if actually linked)
                if (operand) line += formatString("<%p>", operand);
                if (linkMap) ;// @TODO: display function from links
            }
            else {
                line += " ";
                
                // (copy OR convert in {})
                if (!OM_IS_LITERAL(addrmode) && OM_IS_CONVERT(addrmode)) line += "{";
                
                switch (addrmode & OM_BASETYPE) {
                    case OM_ENTRY:      line += "*";   break;
                    case OM_INT:        line += "#";   break;
                    case OM_FLOAT:      line += "%";   break;
                    case OM_STR:        line += "$";   break;
                    case OM_STR_CONST:  line += "c$";  break;
                    case OM_ARRAY:      line += "A";   break;
                    case OM_HASH:       line += "H";   break;
                    case OM_ENTITY:
                        if (!OM_IS_LITERAL(addrmode)) line += "e@";
                        break;
                    case OM_OBJECT:
                        if (!OM_IS_LITERAL(addrmode)) line += "@";
                        break;
                    default:
                        break;
                };
                // Subtype modes (any non literal object/entity conversion/copy, all, all_other)
                // @TODO: will also need 2 subtypes on array/hash conversion/copy
                if ((((addrmode & OM_BASETYPE) == OM_ENTITY || (addrmode & OM_BASETYPE) == OM_OBJECT) &&
                    (addrmode & OM_FLAGS) != OM_NO_CONVERT && (addrmode & OM_FLAGS) != OM_INDIRECT &&
                    (addrmode & OM_BASETYPE) != addrmode)
                    || addrmode == OM_ALL || addrmode == OM_ALL_OTHER) {
                    int subtype = *bytecode++;
                    ++used;
                    if (subtype) {
                        line += "'";
                        // @TODO: actual script or type name
                        line += intToStr(subtype);
                        line += "'";
                    }
                }

                if (OM_IS_INDIRECT(addrmode)) line += "<-";
                else if (OM_IS_COPY(addrmode)) line += "}";
                else if (!OM_IS_LITERAL(addrmode) && OM_IS_CONVERT(addrmode)) line += "/Conv}";

                if (OM_IS_GLOBAL(addrmode)) line += "[G]";
                else if (OM_IS_LOCAL(addrmode)) line += "[L]";
                else if (OM_IS_STACK(addrmode)) line += "[S]";
                else if (OM_IS_POP(addrmode)) line += "[Pop]";
                // (else literal)
                
                if (!OM_IS_POP(addrmode)) {
                    if (OM_IS_LITERAL(addrmode)) {
                        switch (addrmode & OM_BASETYPE) {
                            case OM_INT:
                                line += intToStr(*bytecode++);
                                ++used;
                                break;
                            case OM_FLOAT: {
                                BCfloat operand = *(BCfloat*)(bytecode);
                                bytecode += bcFloatSize;
                                used += bcFloatSize;
                                line += formatString("%"BC_FLOAT_PRINTF, operand);
                                break;
                            }
                            case OM_STR_CONST: {
                                int showString = *bytecode;
                                line += intToStr(showString);
                                ++used;
                                // (show actual string)
                                line += "\"";
                                line += (const char*)(bytecode + showString);
                                line += "\"";
                                ++bytecode;
                                break;
                            }
                            case OM_ALL:
                                line += "ALL";
                                break;
                            case OM_ALL_OTHER:
                                line += "ALL_OTHER";
                                break;
                            case OM_NOTHING:
                                line += "NOTHING";
                                break;
                            case OM_THIS:
                                line += "THIS";
                                break;
                            default:
                                break;
                        };
                    }
                    else if (OM_IS_GLOBAL(addrmode)) {
                        void* operand = *(void**)(bytecode);
                        bytecode += vPtrSize;
                        used += vPtrSize;
                
                        // (only show address if actually linked)
                        if (operand) line += formatString("<%p>", operand);
                        if (linkMap) {
                            LinkMap::iterator found = linkMap->find(linkPos + used);
                            if (found != linkMap->end()) line += (*found).second->name.c_str();
                        }
                    }
                    else {
                        line += intToStr(*bytecode++);
                        ++used;
                    }
                }
            }
        }
    }
    
    return used;
}
